/* @flow */

import config from '../config'
import { initProxy } from './proxy'
import { initState } from './state'
import { initRender } from './render'
import { initEvents } from './events'
import { mark, measure } from '../util/perf'
import { initLifecycle, callHook } from './lifecycle'
import { initProvide, initInjections } from './inject'
import { extend, mergeOptions, formatComponentName } from '../util/index'

let uid = 0

export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // a uid
    //为每个Vue实例定义一个唯一的标识，作用？
    //主要用于在性能追踪时标记每一个Vue实例，以及在transition组件中生成一个
    //唯一的id。
    vm._uid = uid++

    let startTag, endTag
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      startTag = `vue-perf-init:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }

    // a flag to avoid this being observed
    vm._isVue = true
    // merge options
    //_isComponent是在core/vnode/create-component.js-->createComponentInstanceForVnode方法中设置的，
    //也就是说_isComponent属性是在创建内部子组件时设置的。
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      //为什么内部子组件创建时不需要调用resolveConstructorOptions方法和mergeOptions对选项进行合并、解析等？
      //首先需要搞清楚什么情况下创建的Vue实例不是内部子组件的实例，就是通过new Vue(options)(或子类型）创建的Vue实例，
      //此时new对应的类型可能是Vue的任意层级的子孙类型，这些子孙类型的选项对象可能会改变。而对于内部子组件对应的类型，都是Vue的直接
      //子类型，而Vue的选项不建议修改，所以对于内部子组件，无需合并、解析选项，因为在创建Vue子类型时，已经执行了合并、解析过程。
      initInternalComponent(vm, options)
    } else { //执行else的情况是通过new操作直接创建Vue实例，而执行if是创建内部子组件的Vue实例。
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor), // 解析当前构造函数及其父构造函数中的选项
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)
    } else {
      //_renderProxy的含义与用处
      //_renderProxy主要用于作为render或者staticRenderFns的运行时上下文，保证这两个函数的运行时
      //上下文与createElement的第一个参数不同，也就是保证vnode对象的context属性不等于render函数的
      //运行时上下文，防止在render函数中覆盖当前组件的vnode对象的context属性和this.$options._renderChildren中的
      //vnode等的对象的context属性，比如在resolveSlots方法中的child.context === context || child.functionalContext === context比较，
      //就是这里的代理对象的一个应用，详情可查看其注释。
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')

    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`${vm._name} init`, startTag, endTag)
    }

    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

//该方法中的大部分属性都是在createComponentInstanceForVnode方法中设置
function initInternalComponent (vm: Component, options: InternalComponentOptions) {
  //注意，这里是重点，初始化的$options对象的值继承了构造函数的选项对象，所以
  //$options属性可以访问构造函数选项对象中的属性
  const opts = vm.$options = Object.create(vm.constructor.options)
  // doing this because it's faster than dynamic enumeration.
  //parent是vue实例
  opts.parent = options.parent
  opts.propsData = options.propsData //props数据
  opts._parentVnode = options._parentVnode
  opts._parentListeners = options._parentListeners
  opts._renderChildren = options._renderChildren
  opts._componentTag = options._componentTag
  opts._parentElm = options._parentElm
  opts._refElm = options._refElm

  //innerTemplate
  if (options.render) {
    opts.render = options.render
    opts.staticRenderFns = options.staticRenderFns
  }
}

/**
 * 该方法用于解析当前构造函数及其祖先构造函数的选项对象，该选项对象用于创建Vue实例。
 * 如果祖先构造函数的选项对象发生了变化（这种变化是通过Ctor.options = {}的方式修改的），则会重新合并解析选项对象，获得最新改变之后的选项对象。
 * 在该方法中主要执行了三步：
 * 1）查看当前构造函数的所有祖先构造函数的选项对象是否发生改变，如果发生改变，则获得最新的祖先构造函数
 * 的选项对象superOptions。
 * 2）查看当前构造函数的选项对象是否发生改变，如果发生改变，则将最新的当前构造函数的选项对象的修改合并到一个对象
 * 中，并将该对象合并到当前构造函数的extendOptions中。
 * 3）将superOptions和extendOptions合并，获得最新的构造函数的option对象。
 * @param Ctor 指向Vue构造函数
 * @returns {*}
 */
export function resolveConstructorOptions (Ctor: Class<Component>) {
  let options = Ctor.options //获得当前构造函数的选项对象
  //super的作用？ 答：super指向构造函数的父构造函数，存在super属性，则说明
  //该构造函数是Vue的子类型，而不是Vue构造函数。
  if (Ctor.super) {// 如果没有super属性，则说明是Vue构造函数
    // superOptions存储着父构造函数中最新的选项对象
    const superOptions = resolveConstructorOptions(Ctor.super) // 获得祖先构造函数的最新的选项对象
    // cachedSuperOptions存储着父类中最初的选项对象
    const cachedSuperOptions = Ctor.superOptions // 通过Vue.extend方法创建子类型时，会通过superOptions保存父类型的选项对象
    //如果祖先构造函数的选项发生改变，则需要重新解析新的选项
    //注意，这里使用了!==符号，所以，只有父元素的选项对象被替换，才会进入if块。
    /**
     * 示例：
     * let One = Vue.extend({...});
     * let Two = One.extend({...});
     *
     * One.options.methods = {
     *    hello(){}
     * };
     *
     * let vm = new Two({...});
     *
     * 此时不会进入if，因为Two.superOptions与One.options指向同一对象，所以这样的设置不会在Two构造产生创建的Vue实例中访问One的hello()方法
     *
     * 如下做才有效果：
     * One.options = {
     *    methods:{
     *        hello(){}
     *    }
     * };
     * 直接覆盖One.options属性
     */
    if (superOptions !== cachedSuperOptions) {
      // super option changed,
      // need to resolve new options.
      Ctor.superOptions = superOptions
      // check if there are any late-modified/attached options (#4976)
      // 检查当前构造函数的options选项对象中是否有最新修改或新增的选项
      //为什么只在superOptions !== cachedSuperOptions时，才检测当前构造函数的option对象中是否有修改的选项？
      //答：如果父构造函数的选项没有改变，则无需查看当前构造函数的选项的改变，因为这些选项的改变总是最新的，无需每次都
      //检查，只有在父构造函数的选项改变后，因为需要修改当前构造函数的选项对象，所以需要获得最新的extendOptions，用于调用
      //mergeOptions方法，使用最新的superOptions与extendOptions以合并获得最新的选项对象。
      const modifiedOptions = resolveModifiedOptions(Ctor)
      // update base extend options
      if (modifiedOptions) {
        // 将最新修改的选项合并到extendOptions对象中。
        extend(Ctor.extendOptions, modifiedOptions)
      }
      //在extend方法中，options对象就是通过祖先构造函数的选项与extendOptions对象合并获得的。
      //获得最新的祖先构造函数的选项与extendOptions的对象，并将其合并。
      options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
      if (options.name) {
        options.components[options.name] = Ctor
      }
    }
  }
  return options
}

//该方法主要是检测相对于当前构造函数的extendOptions对象是否有更新的选项，
//具体为什么是相对于extendOptions对象，请参见dedupe方法中的注释。
function resolveModifiedOptions (Ctor: Class<Component>): ?Object {
  let modified
  const latest = Ctor.options // 构造函数的当前选项
  const extended = Ctor.extendOptions //通过extend方法创建类型时，传入extend方法的参数。
  const sealed = Ctor.sealedOptions //通过extend方法创建类型时，该类型的options选项对象的拷贝副本。
  for (const key in latest) {
    // 获取所有在最新选项中的键值与最初创建构造函数时的选项的值不同的选项的键与值
    // 也就是构造函数的选项对象在构造函数创建完成之后发生了改变。
    if (latest[key] !== sealed[key]) {
      if (!modified) modified = {}
      modified[key] = dedupe(latest[key], extended[key], sealed[key])
    }
  }
  return modified
}

//sealed存放着最初创建构造函数时的选项的值，也就是最原始的值。
function dedupe (latest, extended, sealed) {
  // compare latest and sealed to ensure lifecycle hooks won't be duplicated
  // between merges
  if (Array.isArray(latest)) {
    const res = []
    sealed = Array.isArray(sealed) ? sealed : [sealed]
    extended = Array.isArray(extended) ? extended : [extended]
    for (let i = 0; i < latest.length; i++) {
      // push original options and not sealed options to exclude duplicated options
      // resolveModifiedOptions方法用于解析最新的options选项中是否有改变的属性，并收集改变的属性，
      // 这里改变的含义包括修改和新增，最后收集的改变会合并到extendOptions对象中。所以
      // extended.indexOf(latest[i]) >= 0 说明是对选项的值进行了修改 也就是修改
      // sealed.indexOf(latest[i]) < 0 说明是新增了之前选项没有的键值对 也就是新增。
      //那什么情况下该if中的判断为false，就是在extended中不存在，但是在sealed中存在的值，说明该值
      //是通过当前构造函数的祖先构造函数的选项获得的，而该方法主要是为了检查与extendOptions对象
      //有关的选项的修改。
      if (extended.indexOf(latest[i]) >= 0 || sealed.indexOf(latest[i]) < 0) {
        res.push(latest[i])
      }
    }
    return res
  } else {
    return latest //如果最新的值不是数组，则直接返回。
  }
}
